/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package de.saar.penguin.give3dreplay.eyetracking;

import com.jme.bounding.BoundingBox;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.system.DisplaySystem;
import give.client.client3d.sceneobject.SceneObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author koller
 */
public class FixationInterpreter {

    private Map<String, SceneObject> objectsByName;
    private List<String> visibleObjects;

    private double MAX_ANGLE_FACTOR = 1;
    private double MAX_EXTRA_ANGLE = 0.05;

    public FixationInterpreter(Map<String, SceneObject> objectsByName) {
        this.objectsByName = objectsByName;
        visibleObjects = new ArrayList<String>();
    }

    public void setVisibleObjects(List<String> visibleObjects) {
        this.visibleObjects = visibleObjects;
    }

    // calculate what objects are close to x,y
    public List<String> getFixatedObjects(int x, int y) {
        List<String> ret = new ArrayList<String>();
        Map<String,Double> anglesFixationToCenter = new HashMap<String, Double>();

        Vector3f eyePosition = new Vector3f();
        Vector3f fixationDirection = getVectorForScreenCoordinates(x, y, eyePosition);

//        System.err.print("    -> fixation dir: " + fixationDirection);
//        System.err.println("\n\n\nvisible: " + visibleObjects);
        for (String obj : visibleObjects) {
            Vector3f vectorToCenter = new Vector3f();
            double angleCornersToCenter = computeAngleToObjectCorners(obj, eyePosition, vectorToCenter);
            double angleFixationToCenter = Math.abs(fixationDirection.angleBetween(vectorToCenter));

//            System.err.println(obj + ":" + angleFixationToCenter + ":" + angleCornersToCenter);

            if (angleFixationToCenter / angleCornersToCenter < MAX_ANGLE_FACTOR || angleFixationToCenter-angleCornersToCenter <= MAX_EXTRA_ANGLE) {
                ret.add(obj);
                anglesFixationToCenter.put(obj, angleFixationToCenter);
            } else {
//                System.err.println("ignore " + obj + ": angle quotient is " + angleFixationToCenter/angleCornersToCenter);
            }
        }

        Collections.sort(ret, new AngleComparator(anglesFixationToCenter));
//        System.err.println("sorted: " + anglesFixationToCenter);
        
        return ret;
    }


    private static class AngleComparator implements Comparator<String> {
        private Map<String,Double> angles;

        public AngleComparator(Map<String, Double> angles) {
            this.angles = angles;
        }

        public int compare(String o1, String o2) {
            if( angles.get(o1) < angles.get(o2) ) {
                return -1;
            } else if( angles.get(o1).equals(angles.get(o2))) {
                return 0;
            } else {
                return 1;
            }
        }
    }

    private double computeAngleToObjectCorners(String obj, Vector3f eyePosition, Vector3f vectorToCenter) {
        // compute bounding box for the object
        SceneObject sobj = objectsByName.get(obj);

        if( sobj == null ) {
            System.err.println("**** WARNING: " + obj + " has no spatial!");
            return 0;
        } else if (sobj.getSpatials().get(0).getWorldBound() == null) {
            System.err.println("**** WARNING: " + obj + " has no bounding box!");
            return 0;
        }

        BoundingBox bbox = (BoundingBox) sobj.getSpatials().get(0).getWorldBound().clone(null);
        for (int i = 1; i < sobj.getSpatials().size(); i++) {
            bbox.mergeLocal(sobj.getSpatials().get(i).getWorldBound());
        }

        // vector from camera to object centerpoint
        vectorToCenter.set(bbox.getCenter());
        vectorToCenter.subtractLocal(eyePosition);
        vectorToCenter.normalizeLocal();

        // compute maximal angle between (corner-eyePosition) and fixationDirection, over all corners
        double maxAngle = -1;
        for (int x = -1; x <= 1; x += 2) {
            for (int y = -1; y <= 1; y += 2) {
                for (int z = -1; z <= 1; z += 2) {
                    Vector3f corner = bbox.getCenter().add(x * bbox.xExtent, y * bbox.yExtent, z * bbox.zExtent);
                    Vector3f directionToCorner = corner.subtract(eyePosition).normalize();
                    double angle = Math.abs(vectorToCenter.angleBetween(directionToCorner));

                    if (angle > maxAngle) {
                        maxAngle = angle;
                    }
                }
            }
        }

        return maxAngle;
    }

    private static Vector3f getVectorForScreenCoordinates(int x, int y, Vector3f eyePosition) {
        Vector2f screenPos = new Vector2f(x,y);

        //get the world location of that X,Y value
        Vector3f worldCoords = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 0);
        Vector3f worldCoords2 = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 1);

        // create a ray starting from the camera, and going in the
        // direction of the mouse's location
        eyePosition.set(worldCoords);
        return worldCoords2.subtract(worldCoords).normalize();
    }
}
